/*
 * Class
 * ExponentialDistribution
 */

package pl.abstractvoid.distributions;

import java.util.Random;
import java.util.TreeMap;
import pl.abstractvoid.datamodel.parameters.ParameterSet;
import pl.abstractvoid.datamodel.parameters.SingleParameter;
import pl.abstractvoid.datamodel.parameters.exceptions.NoSuchParameterException;
import pl.abstractvoid.distributions.exceptions.ParameterValidationFailException;
import pl.abstractvoid.rconnector.RCallerInstance;
import pl.abstractvoid.rconnector.RInterpreter;
import rcaller.exception.ParseException;

/**
 * Represents exponential distribution.
 * 
 * @author Wojciech Szałapski
 */
public class ExponentialDistribution extends AbstractDistribution {

    /**
     * String representing the name of the variable which stores the inverse
     * scale parameter in R.
     */
    private final static String inverseScaleString = "inverseScale";
    /**
     * Names for output values used by R interpreter.
     */
    private final static String[] outputTableNames;
    /**
     * Names for output values used by user interface.
     */
    private final static String[] outputTableVisibleNames;
    /**
     * Buffer for the script executed when the output is computed.
     */
    private final static StringBuilder exponentialSetUpOutputScript = new StringBuilder();
    /**
     * Buffer for the script executed when the cumulative distribution plot is
     * generated.
     */
    private final static StringBuilder exponentialSetUpCumulativeDistributionPlotScript = new StringBuilder();
    /**
     * Buffer for the script executed when the probability density/mass function 
     * plot is generated.
     */
    private final static StringBuilder exponentialSetUpProbabilityPlotScript = new StringBuilder();
    
    /*
     * Initialization of:
     *  - Names of output parameters.
     *  - Script for computing output parameters.
     *  - Scripts for generating plots.
     */
    static {
        final int numberOfParameters = 3;
        outputTableNames = new String[numberOfParameters];
        outputTableVisibleNames = new String[numberOfParameters];
        outputTableNames[0] = "expectedValue";
        outputTableNames[1] = "variance";
        outputTableNames[2] = "standardDeviation";
        
        exponentialSetUpOutputScript.append(outputTableNames[0]).append(" = ").
                append(outputTableNames[2]).append(" = 1 / ").
                append(RInterpreter.inputRList).append("$").append(inverseScaleString).append("\n");
        exponentialSetUpOutputScript.append(outputTableNames[1]).append(" = ").
                append(outputTableNames[0]).append("^2\n");
        exponentialSetUpOutputScript.append(RInterpreter.outputRList).append(" = list(");
        String prefix = "";
        for (String name : outputTableNames) {
            exponentialSetUpOutputScript.append(prefix).append(name).append(" = ").append(name);
            prefix = ", ";
        }
        exponentialSetUpOutputScript.append(")");
        
        final int plotPrecision = 5000;
        exponentialSetUpCumulativeDistributionPlotScript.append(
                "xMinimumValue = 0\n").
                append("xMaximumValue = qexp(0.9999, ").
                append(RInterpreter.inputRList).append("$").append(inverseScaleString).append(")\n").
                append("plotX = seq(xMinimumValue, xMaximumValue, length.out = ").append(plotPrecision).append(")\n"
                + "normalPlot(plotX, pexp(plotX, ").
                append(RInterpreter.inputRList).append("$").append(inverseScaleString).append("), "
                + "type = 'l')");
        
        exponentialSetUpProbabilityPlotScript.append(exponentialSetUpCumulativeDistributionPlotScript.
                toString().replaceAll("pexp", "dexp"));
    }
    
    /**
     * Constructs the distribution using given input parameters.
     * 
     * @param inverseScale Inverse scale parameter.
     * @throws ParameterValidationFailException
     * @throws NoSuchParameterException 
     */
    public ExponentialDistribution(double inverseScale)
            throws ParameterValidationFailException, NoSuchParameterException {
        super(exponentialSetUpOutputScript, exponentialSetUpCumulativeDistributionPlotScript, 
              exponentialSetUpProbabilityPlotScript);
        distributionName = "exponentialDistribution";
        rState = new RCallerInstance();
        SingleParameter inverseScaleParam = new SingleParameter(inverseScaleString);
        inverseScaleParam.setParameterValue(inverseScale);
        TreeMap<String, SingleParameter> singleParameters = new TreeMap<>();
        singleParameters.put(inverseScaleString, inverseScaleParam);
        ParameterSet parameterSet = new ParameterSet();
        parameterSet.setSingleParameters(singleParameters);
        updateInput(parameterSet);
    }

    @Override
    protected void validate(ParameterSet parameters) throws ParameterValidationFailException {
        TreeMap<String, SingleParameter> singleParameters = parameters.getSingleParameters();
        SingleParameter inverseScaleParam = singleParameters.get(inverseScaleString);
        if (inverseScaleParam.getParameterValue() <= 0) {
            throw new ParameterValidationFailException("invalidInverseScaleParameter");
        }
    }

    @Override
    public ParameterSet loadParameters() throws NoSuchParameterException, ParseException {
        rState.runCustomCode("", RInterpreter.inputRList);
        SingleParameter inverseScaleParam = new SingleParameter(inverseScaleString);
        inverseScaleParam.setParameterValue(getSingleParameterValue(inverseScaleString));
        TreeMap<String, SingleParameter> singleParameters = new TreeMap<>();
        singleParameters.put(inverseScaleString, inverseScaleParam);
        ParameterSet result = new ParameterSet();
        result.setSingleParameters(singleParameters);
        return result;
    }

    @Override
    protected void initializeOutputTable() {
        outputTable = new SingleParameter[outputTableNames.length];
        for (int i = 0; i < outputTableNames.length; ++i) {
            outputTable[i] = new SingleParameter(outputTableNames[i]);
        }
    }
    
    /**
     * Returns a new instance of the exponential distribution with random
     * inverse parameter scale (from 0.001 to 5).
     * 
     * @return New instance of exponential distribution with random parameters.
     */
    public static ExponentialDistribution getSampleDistributionData() {
        Random generator = new Random();
        double inverseScale = generator.nextDouble() * 5;
        if (inverseScale == 0) inverseScale = 0.001;
        try {
            return new ExponentialDistribution(inverseScale);
        } catch (ParameterValidationFailException | NoSuchParameterException ex) {
            return null;
        }
    }
}
